using System;
using System.Collections.Generic;

namespace Gj.Cache
{
    public class LRUCache<K, V> : ICache<K, V>
    {
        private readonly int capacity;
        private int count;
        private readonly LRUDoubleLinkedList<K, V> doubleLinkedList;
        private readonly Dictionary<K, LRUNode<K, V>> map;
        private readonly Action<K, V> onRemove;


        public LRUCache(int capacity, Action<K, V> onRemoveCallback)
        {
            this.capacity = capacity;
            count = 0;
            map = new Dictionary<K, LRUNode<K, V>>(capacity);
            doubleLinkedList = new LRUDoubleLinkedList<K, V>();

            onRemove = onRemoveCallback;
        }

        // each time when access the node, we move it to the top
        public bool Get(K key, out V value)
        {
            if (!map.ContainsKey(key))
            {
                value = default;
                return false;
            }

            var node = map[key];
            doubleLinkedList.RemoveNode(node);
            doubleLinkedList.AddToTop(node);
            value = node.Value;
            return true;
        }

        public bool Contains(K key)
        {
            return map.ContainsKey(key);
        }

        public void Add(K key, V value)
        {
            // just need to update value and move it to the top
            if (map.ContainsKey(key))
            {
                var node = map[key];
                doubleLinkedList.RemoveNode(node);
                node.Value = value;
                doubleLinkedList.AddToTop(node);
            }
            else
            {
                // if cache is full, then remove the least recently used node
                if (count == capacity)
                {
                    var lru = doubleLinkedList.RemoveLRUNode();
                    map.Remove(lru.Key);
                    count--;
                    onRemove?.Invoke(lru.Key, lru.Value);
                }

                // add a new node
                var node = new LRUNode<K, V>(key, value);
                doubleLinkedList.AddToTop(node);
                map[key] = node;
                count++;
            }
        }
    }
}